
/*
 * 06.05.2007 niXman, i dot nixman at gmail dot com
 * UP 10.02.2009
 * UP 24.07.2009
 * UP 25.07.2009
 * UP 06.11.2009
 * UP 01.05.2011
 * UP 22.12.2011
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301  USA.
 *
 */

#include <assert.h>
#include <string.h>
#include <stdlib.h>

#include "mfio.h"

#ifdef __cplusplus
namespace mfio {
#endif

/****************************************************************************/

file_header* init(const char* name, const char* mode) {
   file_header* internal_file = NULL;

   MFIO_DEBRET(name && *name && mode && *mode && (*mode== 'r' || *mode == 'w'), 0);
   MFIO_DEBRET((internal_file = (file_header*)malloc(sizeof(file_header))), 0);

   internal_file->is_mfio[0] = 'M';
   internal_file->is_mfio[1] = 'F';
   internal_file->arr_size = 32;
   internal_file->arr = malloc(internal_file->arr_size);
   internal_file->index = 0;
   internal_file->name = strdup(name);
   internal_file->mode = *mode;
   mfio_mutex_init(&internal_file->mutex);

   return internal_file;
}

/****************************************************************************/

void clean(file_header* internal_file) {
   if ( internal_file ) {
      mfio_mutex_lock(&internal_file->mutex);
      if ( internal_file->arr ) {
         free(internal_file->arr);
         internal_file->arr = NULL;
      }
      if ( internal_file->name ) {
         free(internal_file->name);
         internal_file->name = NULL;
      }
      internal_file->mode		= 0;
      internal_file->arr_size	= 0;
      internal_file->index		= 0;
      mfio_mutex_unlock(&internal_file->mutex);
      mfio_mutex_destroy(&internal_file->mutex);
   }
}

/****************************************************************************/

int is_readable(FILE* mfio_file) {
   return (MFIO_FH(mfio_file)->mode == 'r');
}

/****************************************************************************/

int is_writable(FILE* mfio_file) {
   return (MFIO_FH(mfio_file)->mode == 'w');
}

/****************************************************************************/

FILE* mfopen(const char* name, const char* mode) {
   FILE* mfio_file;
   file_header* internal_file;

   mfio_file = (FILE*)malloc(sizeof(FILE));
   MFIO_DEBRET(mfio_file, 0);
   ZEROPOD(mfio_file);
   internal_file = init(name, mode);
   MFIO_FP(mfio_file) = (char*)internal_file;

   return mfio_file;
}

/****************************************************************************/

int mfclose(FILE* mfio_file) {
   MFIO_DEBRET(mfio_file != NULL, -1);
   if ( MFIO_FH(mfio_file) ) {
      clean(MFIO_FH(mfio_file));
      free(MFIO_FH(mfio_file));
      MFIO_FP(mfio_file) = NULL;
   }
   return 0;
}

/****************************************************************************/

int mfeof(FILE* mfio_file) {
   MFIO_DEBRET(mfio_file != NULL, -1);
   if ( !is_readable(mfio_file) ) {
      return 1;
   }
   return (MFIO_FH(mfio_file)->index >= MFIO_FH(mfio_file)->arr_size);
}

/****************************************************************************/

int mfputc(char ch, FILE* mfio_file) {
   MFIO_DEBRET(is_writable(mfio_file), -1);
   if ( MFIO_FH(mfio_file)->arr ) {
      if ( MFIO_FH(mfio_file)->index + sizeof(ch) > MFIO_FH(mfio_file)->arr_size ) {
         if ( mfwrite(&ch, sizeof(ch), 1, mfio_file) != sizeof(ch) ) {
            return -1;
         }
      } else {
         *(MFIO_FH(mfio_file)->arr + MFIO_FH(mfio_file)->index++) = ch;
      }
      return ch;
   }
   return -1;
}

/****************************************************************************/

int mfputs(const char* s, FILE* mfio_file) {
   MFIO_DEBRET(is_writable(mfio_file), -1);
   if ( s && *s ) {
      return mfwrite(s, strlen(s), 1, mfio_file) + mfputc('\n', mfio_file);
   }
   return -1;
}

/****************************************************************************/

int mfgetc(FILE* mfio_file) {
   MFIO_DEBRET(is_readable(mfio_file), -1);
   if ( MFIO_FH(mfio_file)->arr ) {
      if ( MFIO_FH(mfio_file)->index + 1 > MFIO_FH(mfio_file)->arr_size ) {
         return -1;
      }
      return (u_int8_t)*(MFIO_FH(mfio_file)->arr + MFIO_FH(mfio_file)->index++);
   }
   return -1;
}

/****************************************************************************/

char* mfgets(char* buffer, int len, FILE* mfio_file) {
   int i = 0;
   char ch;
   if ( !buffer || !len ) { return NULL; }
   MFIO_DEBRET(is_readable(mfio_file), 0);

   do {
      ch = mfgetc(mfio_file);
      if ( ch == 0 || ch == -1 ) {
         return NULL;
      }
      buffer[i] = ch;
      if ( ch == '\n' ) {
         break;
      }
   } while ( i++ < len );
   buffer[i] = 0;
   return buffer;
}

/****************************************************************************/

int mungetc(int ch, FILE* mfio_file) {
   MFIO_DEBRET(is_readable(mfio_file), -1);
   if ( MFIO_FH(mfio_file) ) {
      if ( MFIO_FH(mfio_file)->index ) {
         MFIO_FH(mfio_file)->index--;
      } else {
         ch = -1;
      }
   }
   return ch;
}

/****************************************************************************/

int mfseek(FILE* mfio_file, long offset, int direction) {
   if ( mfio_file ) {
      if ( MFIO_FH(mfio_file) ) {
         switch ( direction ) {
            case SEEK_SET:
               if ( MFIO_FH(mfio_file)->arr_size < (u_int32_t)offset ) {
                  offset = MFIO_FH(mfio_file)->arr_size;
               }
               MFIO_FH(mfio_file)->index = offset;
               break;
            case SEEK_CUR:
               if ( MFIO_FH(mfio_file)->index + offset > MFIO_FH(mfio_file)->arr_size ) {
                  offset = MFIO_FH(mfio_file)->arr_size - MFIO_FH(mfio_file)->index;
               }
               MFIO_FH(mfio_file)->index = offset;
               break;
            case SEEK_END:
               if ( (u_int32_t)offset > MFIO_FH(mfio_file)->arr_size ) {
                  offset = MFIO_FH(mfio_file)->arr_size;
               }
               MFIO_FH(mfio_file)->index = MFIO_FH(mfio_file)->arr_size - offset;
               break;
            default:
               break;
         }
      }
   }
   return 0;
}

/****************************************************************************/

long int mftell(FILE* mfio_file) {
   if ( mfio_file && MFIO_FH(mfio_file) ) {
      return MFIO_FH(mfio_file)->index;
   }
   return -1;
}

/****************************************************************************/

int mfread(void* ptr, size_t size, size_t nitems, FILE* mfio_file) {
   MFIO_DEBRET(ptr, -1);
   MFIO_DEBRET(is_readable(mfio_file), -1);
   if ( MFIO_FH(mfio_file) && MFIO_FH(mfio_file)->arr ) {
      u_int32_t len = size*nitems;
      if ( MFIO_FH(mfio_file)->index + len > MFIO_FH(mfio_file)->arr_size ) {
         if ( MFIO_FH(mfio_file)->index >= MFIO_FH(mfio_file)->arr_size ) {
            return -1;
         } else {
            len = MFIO_FH(mfio_file)->arr_size - MFIO_FH(mfio_file)->index;
         }
      }
      memcpy(ptr, MFIO_FH(mfio_file)->arr + MFIO_FH(mfio_file)->index, len);
      MFIO_FH(mfio_file)->index += len;
      return nitems;
   }
   return -1;
}

/****************************************************************************/

int mfwrite(const void* ptr, size_t size, size_t nitems, FILE* mfio_file) {
   const u_int32_t to_write = size*nitems;
   MFIO_DEBRET(to_write && is_writable(mfio_file), -1);
   if ( MFIO_FH(mfio_file) && MFIO_FH(mfio_file)->arr ) {
      if ( MFIO_FH(mfio_file)->index + to_write > MFIO_FH(mfio_file)->arr_size ) {
         MFIO_FH(mfio_file)->arr_size+=(to_write*4);
         char* buf = malloc(MFIO_FH(mfio_file)->arr_size);
         MFIO_DEBRET(buf, -1);
         memcpy(buf, MFIO_FH(mfio_file)->arr, MFIO_FH(mfio_file)->index);
         free(MFIO_FH(mfio_file)->arr);
         MFIO_FH(mfio_file)->arr = buf;
      }
      memcpy(MFIO_FH(mfio_file)->arr + MFIO_FH(mfio_file)->index, ptr, to_write);
      MFIO_FH(mfio_file)->index += to_write;
   }
   return to_write;
}

/****************************************************************************/

int is_mfio_file(FILE* file) {
   MFIO_DEBRET(file, 0);
   return (strncmp(MFIO_FH(file)->is_mfio, "MF", 2) == 0);
}

/****************************************************************************/

int is_real_file(FILE* file) {
   return !is_mfio_file(file);
}

/****************************************************************************/

char* get_name(FILE* mfio_file) {
   MFIO_DEBRET(is_mfio_file(mfio_file), NULL);
   if ( MFIO_FH(mfio_file) ) {
      return MFIO_FH(mfio_file)->name;
   }
   return NULL;
}

/****************************************************************************/

char* get_mode(FILE* mfio_file) {
   MFIO_DEBRET(is_mfio_file(mfio_file), NULL);
   if ( MFIO_FH(mfio_file) ) {
      return &MFIO_FH(mfio_file)->mode;
   }
   return NULL;
}

/****************************************************************************/

void set_mode(const char* mode, FILE* mfio_file) {
   MFIO_DEBBRK(mode && *mode && is_mfio_file(mfio_file));
   if ( MFIO_FH(mfio_file) ) {
      MFIO_FH(mfio_file)->mode = *mode;
   }
}

/****************************************************************************/

void set_writable(FILE* mfio_file) {
   MFIO_DEBBRK(is_mfio_file(mfio_file) && is_writable(mfio_file));
   set_mode("w", mfio_file);
}

/****************************************************************************/

void set_readable(FILE* mfio_file) {
   MFIO_DEBBRK(is_mfio_file(mfio_file) && is_readable(mfio_file));
   set_mode("r", mfio_file);
}

/****************************************************************************/

char* get_buffer(FILE* mfio_file) {
   MFIO_DEBRET(is_mfio_file(mfio_file), 0);
   if ( MFIO_FH(mfio_file) ) {
      return MFIO_FH(mfio_file)->arr;
   }
   return 0;
}

/****************************************************************************/

u_int32_t get_buffer_size(FILE* mfio_file) {
   return mftell(mfio_file);
}

/****************************************************************************/

char* set_buffer(FILE* mfio_file, char* buf, int len) {
   MFIO_DEBRET(mfio_file && MFIO_FH(mfio_file) && is_mfio_file(mfio_file) && buf && len, NULL);
   if ( MFIO_FH(mfio_file)->arr ) {
      free(MFIO_FH(mfio_file)->arr);
   }
   MFIO_FH(mfio_file)->arr = buf;
   MFIO_FH(mfio_file)->arr_size = len;
   MFIO_FH(mfio_file)->index = 0;
   return MFIO_FH(mfio_file)->arr;
}

/****************************************************************************/

FILE* load_from_file(const char* name) {
   MFIO_DEBRET(name && *name, 0);
   FILE* mfio_file;
   u_int32_t size;
   file_header* internal_file;

   FILE* fin = fopen(name, "rb");
   MFIO_DEBRET(fin, 0);
   fseek(fin, 0, SEEK_END);
   size = ftell(fin);
   fseek(fin, 0, SEEK_SET);

   mfio_file = (FILE*)malloc(sizeof(FILE));
   MFIO_DEBRET(mfio_file, 0);
   ZEROPOD(mfio_file);

   internal_file = init(name, "rb");
   MFIO_FP(mfio_file) = (char*)internal_file;

   internal_file->arr = realloc(internal_file->arr, size + 1);
   assert(internal_file->arr);
   assert(fread(internal_file->arr, 1, size, fin) == size);
   fclose(fin);

   internal_file->arr_size = size;
   return mfio_file;
}

/****************************************************************************/

int save_to_file(const char* name, FILE* mfio_file) {
   u_int32_t writen;
   FILE* fout = 0;
   MFIO_DEBRET(name && *name && mfio_file && MFIO_FH(mfio_file) && is_mfio_file(mfio_file), -1);
   fout = fopen(name, "wb");
   MFIO_DEBRET(fout, -1);
   assert((writen = fwrite(MFIO_FH(mfio_file)->arr, 1, MFIO_FH(mfio_file)->arr_size, fout))
          == MFIO_FH(mfio_file)->arr_size);
   fclose(fout);
   return writen;
}

/****************************************************************************/

int resize(FILE* mfio_file, u_int32_t len) {
   MFIO_DEBRET(mfio_file && MFIO_FH(mfio_file) && is_mfio_file(mfio_file), -1);
   MFIO_FH(mfio_file)->arr = realloc(MFIO_FH(mfio_file)->arr, len);
   if ( len ) assert(MFIO_FH(mfio_file)->arr);
   MFIO_FH(mfio_file)->arr_size = len;

   if ( MFIO_FH(mfio_file)->index > len ) {
      MFIO_FH(mfio_file)->index = len;
   }
   return len;
}

/****************************************************************************/

int truncate(FILE* mfio_file) {
   MFIO_DEBRET(mfio_file && MFIO_FH(mfio_file) && is_mfio_file(mfio_file), -1);
   return resize(mfio_file, 0) == 0;
}

/****************************************************************************/

int cat_file(FILE* mfio_file1, FILE* mfio_file2) {
   u_int32_t new_size;
   MFIO_DEBRET(mfio_file1 && mfio_file2 && MFIO_FH(mfio_file1) && MFIO_FH(mfio_file2)
          && is_mfio_file(mfio_file1) && is_mfio_file(mfio_file2), -1);
   if ( is_readable(mfio_file1) ) {
      new_size = MFIO_FH(mfio_file1)->arr_size + MFIO_FH(mfio_file2)->arr_size;
   } else {
      new_size = MFIO_FH(mfio_file1)->index + MFIO_FH(mfio_file2)->arr_size;
   }
   MFIO_FH(mfio_file1)->arr = realloc(MFIO_FH(mfio_file1)->arr, new_size + 1);
   MFIO_DEBRET(MFIO_FH(mfio_file1)->arr, -1);
   if ( is_readable(mfio_file2) ) {
      memcpy((MFIO_FH(mfio_file1)->arr + MFIO_FH(mfio_file1)->arr_size),
             MFIO_FH(mfio_file2)->arr, MFIO_FH(mfio_file2)->arr_size);
   } else {
      memcpy((MFIO_FH(mfio_file1)->arr + MFIO_FH(mfio_file1)->arr_size),
             MFIO_FH(mfio_file2)->arr, MFIO_FH(mfio_file2)->index);
   }
   MFIO_FH(mfio_file1)->arr_size = new_size;

   return new_size;
}

/****************************************************************************/

int cat_file2(FILE* mfio_file, const char* name) {
   FILE* in = load_from_file(name);
   int result = cat_file(mfio_file, in);
   mfclose(in);
   return result;
}

/****************************************************************************/

void report(FILE* mfio_file) {
   if ( mfio_file && MFIO_FH(mfio_file) ) {
      fprintf(stdout,
         "# Name :  %s\n"
         "# Mode :  %c\n"
         "# Buff :  %p\n"
         "# Size :  %d\n"
         "# Index:  %d\n\n",
         MFIO_FH(mfio_file)->name,
         MFIO_FH(mfio_file)->mode,
         MFIO_FH(mfio_file)->arr,
         MFIO_FH(mfio_file)->arr_size,
         MFIO_FH(mfio_file)->index
      );
      fflush(stdout);
   }
}

/****************************************************************************/

#ifdef __cplusplus
} // namespace mfio
#endif
